package dslProgramGen.methodCall;

import dslProgramGen.DslProgramGen;
import dslProgramGen.GlobalVarTableRecord;
import dslProgramGen.WhoPointToThisObjectCollection;
import methodCallTreeGen.MethodNode;
import methodCallTreeGen.TextNode;
import utils.Utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static dslProgramGen.methodCall.MethodReturnObj.genAddNewLocalRootDslStatementForMethodReturnEqualSignLeft;
import static dslProgramGen.util.DslProgramGenUtil.*;
import static methodCallTreeGen.ProcessBeforeMainAndRemovedNodeTraces.globalVarTable;

public class GenMethodCallDsl {

    /**
     * 获得MethodNode的所有Add PR traces行
     *
     * @param methodNode 当前要获得Add PR的MethodNode
     * @return 当前MethodNode的所有Add PR traces行
     */
    public static List<String> getAllAddPrTracesFromMethodNode(MethodNode methodNode) {
        // Add PR H:NULL, C:NULL, T:main, M:Test.myFunctionReturnMethod ()LStudent;
        // Add PR H:0x00000000300f34aa, C:Student, T:main, M:Student.<init> ()V
        List<String> firstTextNodeTracesList = ((TextNode) methodNode.contentNodeList.get(0)).tracesList;
        // 包含Add PR的traces
        List<String> addPrTracesList = new ArrayList<>();
        for (int i = 0; i < firstTextNodeTracesList.size(); i++) {
            if (firstTextNodeTracesList.get(i).contains("Add PR")) {
                addPrTracesList.add(firstTextNodeTracesList.get(i));
            }
        }
        return addPrTracesList;
    }

    /**
     * 获得MethodNode的所有H不为NULL的Add PR traces行
     * 即排除 Add PR H:NULL, C:NULL, T:main, M:Test.myFunctionReturnMethod ()LStudent;行
     *
     * @param methodNode 当前要获得Add PR的MethodNode
     * @return 当前MethodNode的所有H不为NULL的Add PR traces行
     */
    public static List<String> getAllNotNullAddPrTracesFromMethodNode(MethodNode methodNode) {
        // Add PR H:NULL, C:NULL, T:main, M:Test.myFunctionReturnMethod ()LStudent;
        // Add PR H:0x00000000300f34aa, C:Student, T:main, M:Student.<init> ()V

        // 获得MethodNode的所有Add PR行
        List<String> allAddPrTracesList = getAllAddPrTracesFromMethodNode(methodNode);
        // 表示MethodNode的所有H不为NULL的Add PR traces行
        List<String> notNullAddPrTracesList = new ArrayList<>();
        // 处理每一条Add PR语句，将H不为NULL的添加到notNullAddPrTracesList
        for (String addPrLine : allAddPrTracesList) {
            // Add PR H:NULL, C:NULL, T:main, M:Test.myFunctionReturnMethod ()LStudent;
            // Add PR H:0x00000000300f34aa, C:Student, T:main, M:Student.<init> ()V
            // 获得Hid
            String hId = Utils.RegularExpressionProcess(addPrLine, "H:(.*?),");
            if ("NULL".equals(hId)) {
                // 如果传入的参数为NULL，不处理
            } else {
                notNullAddPrTracesList.add(addPrLine);
            }
        }
        return notNullAddPrTracesList;
    }


    /**
     * 产生对象初始化的调用函数 例如产生 Student_____init__(var1, var2) 函数调用DSL语句
     *
     * @param currentMethodNode                    待处理的MethodNode B节点
     * @param dslMethodContentList                 由DSL语句表示的MethodNode A 内容列表
     * @param objId2objExpressionsMap              指定对象id在DSL程序中可以怎样表示出来 <objId, expressions>
     * @param dslCalledSubMethodName2ParameterList 表示该函数调用的子函数名称及其参数列表
     */
    public static void genCallObjectInitMethodDsl(MethodNode currentMethodNode, List<String> dslMethodContentList, Map<String,
            WhoPointToThisObjectCollection> objId2objExpressionsMap, Map<String, List<String>> dslCalledSubMethodName2ParameterList) {
        // 获得MethodNode B的所有Add PR行
        List<String> allAddPrTracesList = getAllAddPrTracesFromMethodNode(currentMethodNode);
        // 初始化函数调用中的参数用DSL语句表示，例如 var1,var2 ;  Student_____init__(var1, var2)
        List<String> parametersDslList = new ArrayList<>();
        // 处理初始化函数的每一条Add PR语句，生成对应的参数DSL表示
        for (String addPrLine : allAddPrTracesList) {
            // Add PR H:NULL, C:NULL, T:main, M:Test.myFunctionReturnMethod ()LStudent;
            // Add PR H:0x00000000300f34aa, C:Student, T:main, M:Student.<init> ()V
            // 对象初始化函数必定传参this，所以H的内容不为NULL
            // 获得Hid
            String hId = Utils.RegularExpressionProcess(addPrLine, "H:(.*?),");
            // 获得Htype
            String hType = Utils.RegularExpressionProcess(addPrLine, "C:(.*?),");
            hType = renameClassNameReplaceSlashWithUnderlineAndRenameObjectClassName(hType);
            if ("NULL".equals(hId)) {
                // 如果传入的参数为NULL，不处理
            } else {
                // 有的对象可能出现用前未新建的情况，判断objId2objExpressionsMap.get(objId)是否为空
                if (tryToGetDslExpressionOfObj(hId, objId2objExpressionsMap) == null) {
                    // 传入参数为null
                    parametersDslList.add("null");
                    continue;
                }
                // 获得hId的DSL表示
                String dslExpressionOfHId = getDslExpressionOfObj(hId, objId2objExpressionsMap);
                if (DslProgramGen.isCastType) {
                    if (isArrayType(hType)) {
                        hType = castArraySignaturToArrayExpr(hType);
                    }
                    dslExpressionOfHId = "(" + hType + ")" + dslExpressionOfHId;
                }
                parametersDslList.add(dslExpressionOfHId);
            }
        }
        // 更改函数名 Student___<init>_v1 为 Student_____init___v1
        String newInitMethodName = currentMethodNode.newMethodName.replace("<", "__").replace(">", "__");
        currentMethodNode.newMethodName = newInitMethodName;
        // 生成函数调用DSL语句
        String callInitMethodDsl = genCallMethodDslFromParameterListAndMethodName(newInitMethodName, parametersDslList);
        // 添加DSL语句到函数体中
        dslMethodContentList.add(callInitMethodDsl);
        // 将调用的初始化函数及参数保存在dslCalledSubMethodName2ParameterList中
        dslCalledSubMethodName2ParameterList.put(newInitMethodName, parametersDslList);
    }


    /**
     * 在知道函数名和参数列表的情况下，生成调用函数的DSL语句，无返回值 例如 f(var1, var2);
     *
     * @param methodName        函数名
     * @param parametersDslList 函数参数列表
     * @return 调用函数DSL语句
     */
    public static String genCallMethodDslFromParameterListAndMethodName(String methodName, List<String> parametersDslList) {

        String callMethodDsl = methodName + "(";
        for (int i = 0; i < parametersDslList.size(); i++) {
            if (i == parametersDslList.size() - 1) {
                // 如果是最后一个参数
                callMethodDsl += parametersDslList.get(i);
                break;
            }
            callMethodDsl += parametersDslList.get(i) + ", ";
        }
        callMethodDsl += ");";
        return callMethodDsl;
    }

    /**
     * 产生函数调用DSL语句（函数没有返回值） 例如产生 f(var1, var2) 函数调用DSL语句
     * 注意 如果参数有在 newForMethodParameterTempLocalRootList列表中，删除 tempStu (f(new Student();),并且生成tempStu=null的DSL语句
     *
     * @param currentMethodNode                    待处理的MethodNode B节点
     * @param dslMethodContentList                 由DSL语句表示的MethodNode A 内容列表
     * @param objId2objExpressionsMap              指定对象id在DSL程序中可以怎样表示出来 <objId, expressions>
     * @param dslCalledSubMethodName2ParameterList 该函数调用的子函数名称及其参数列表
     */
    public static void genCallMethodWithNoReturnDsl(MethodNode currentMethodNode, List<String> dslMethodContentList, Map<String,
            WhoPointToThisObjectCollection> objId2objExpressionsMap, Map<String, List<String>> dslCalledSubMethodName2ParameterList) {
        List<String> newForMethodParameterTempLocalRootList = new ArrayList<>();
        List<String> methodReturnForMethodParameterTempLocalRootList = new ArrayList<>();
        // 生成函数调用DSL语句
        String callMethodDsl = genCallMethodEqualSignRightDsl(newForMethodParameterTempLocalRootList,
                methodReturnForMethodParameterTempLocalRootList,
                currentMethodNode,
                objId2objExpressionsMap,
                dslCalledSubMethodName2ParameterList);
        // 添加DSL语句到函数体中
        dslMethodContentList.add(callMethodDsl);
        // 处理f(new Student())的情况新建的临时local root,生成tempStu=null的DSL语句
        for (String tempLocalRoot : newForMethodParameterTempLocalRootList) {
            dslMethodContentList.add(tempLocalRoot + " = null;");
        }
        // 处理f(g())的情况新建的临时local root,生成tempStu=null的DSL语句
        for (String tempLocalRoot : methodReturnForMethodParameterTempLocalRootList) {
            dslMethodContentList.add(tempLocalRoot + " = null;");
        }
    }

    /**
     * 产生函数调用DSL语句，如果函数调用无返回值，则产生 f(var1, var2);
     * 如果函数调用有返回值，则产生 等号右边的函数调用DSL语句
     *
     * @param newForMethodParameterTempLocalRootList          为new对象创建的临时local root列表,后续需要将其赋值为null，并添加赋值为null的DSL语句到函数体中,例如f(new Student()); 定义的临时local root例如 Student stuTemp = new Student();
     * @param methodReturnForMethodParameterTempLocalRootList 为函数返回值对象创建的临时local root列表,后续需要将其赋值为null，并添加赋值为null的DSL语句到函数体中,例如f(g()); 定义的临时local root例如 Student stuTemp = g();
     * @param currentMethodNode                               待处理的MethodNode B节点
     * @param objId2objExpressionsMap                         指定对象id在DSL程序中可以怎样表示出来 <objId, expressions>
     * @param dslCalledSubMethodName2ParameterList            该函数调用的子函数名称及其参数列表
     * @return 函数调用DSL语句，例如 f(var1, var2);
     */
    public static String genCallMethodEqualSignRightDsl(List<String> newForMethodParameterTempLocalRootList,
                                                        List<String> methodReturnForMethodParameterTempLocalRootList,
                                                        MethodNode currentMethodNode,
                                                        Map<String, WhoPointToThisObjectCollection> objId2objExpressionsMap,
                                                        Map<String, List<String>> dslCalledSubMethodName2ParameterList) {
        // 获得MethodNode B的所有Add PR行
        List<String> allAddPrTracesList = getAllAddPrTracesFromMethodNode(currentMethodNode);
        // 函数调用中的参数用DSL语句表示，例如 var1,var2 ;  f(var1, var2)
        List<String> parametersDslList = new ArrayList<>();
        // 处理函数MethodNode B的每一条Add PR语句，生成对应的参数DSL表示
        for (String addPrLine : allAddPrTracesList) {
            // Add PR H:NULL, C:NULL, T:main, M:Test.myFunctionReturnMethod ()LStudent;
            // Add PR H:0x00000000300f34aa, C:Student, T:main, M:Student.<init> ()V
            // 获得Hid
            String hId = Utils.RegularExpressionProcess(addPrLine, "H:(.*?),");
            // 获得Htype
            String hType = Utils.RegularExpressionProcess(addPrLine, "C:(.*?),");
            hType = renameClassNameReplaceSlashWithUnderlineAndRenameObjectClassName(hType);
            GlobalVarTableRecord globalVarTableRecord = globalVarTable.get(hId);
            if ("NULL".equals(hId)) {
                // 如果传入的参数为NULL，不处理
            } else if (globalVarTableRecord != null) {
                // 再在全局变量表中查一下有没有hId
                // 在全局变量表中找到了
                parametersDslList.add(globalVarTableRecord.dslExpr);
            } else if (objId2objExpressionsMap.get(hId) == null) {
                // 函数调用参数为null
                parametersDslList.add("null");
            } else if (objId2objExpressionsMap.get(hId).newForMethodParameterTempLocalRootList.size() != 0) {
                // f(new Student())的情况
                // 如果参数有在 newForMethodParameterTempLocalRootList列表中，删除 tempStu (f(new Student();),并且生成tempStu=null的DSL语句
                String newForMethodParameterTempLocalRootDsl = objId2objExpressionsMap.get(hId).newForMethodParameterTempLocalRootList.get(0);
                parametersDslList.add(newForMethodParameterTempLocalRootDsl);
                // 删除newForMethodParameterTempLocalRootList列表中的元素
                objId2objExpressionsMap.get(hId).newForMethodParameterTempLocalRootList.remove(0);
                // 记录temp local root，后续生成 tempStu=null的DSL语句
                newForMethodParameterTempLocalRootList.add(newForMethodParameterTempLocalRootDsl);
            } else if (objId2objExpressionsMap.get(hId).methodReturnForMethodParameterTempLocalRootList.size() != 0) {
                // f(g())的情况
                // 如果参数有在 methodReturnForMethodParameterTempLocalRootList列表中，删除 tempStu (f(g());),并且生成tempStu=null的DSL语句
                String methodReturnForMethodParameterTempLocalRootDsl = objId2objExpressionsMap.get(hId).methodReturnForMethodParameterTempLocalRootList.get(0);
                parametersDslList.add(methodReturnForMethodParameterTempLocalRootDsl);
                // 删除methodReturnForMethodParameterTempLocalRootList列表中的元素
                objId2objExpressionsMap.get(hId).methodReturnForMethodParameterTempLocalRootList.remove(0);
                // 记录temp local root，后续生成 tempStu=null的DSL语句
                methodReturnForMethodParameterTempLocalRootList.add(methodReturnForMethodParameterTempLocalRootDsl);
            } else if (objId2objExpressionsMap.get(hId).lrAndPrPointToThisObjectList.size() != 0) {
                // 获得hId的DSL表示
                String dslExpressionOfHId = getDslExpressionOfObj(hId, objId2objExpressionsMap);
                if (DslProgramGen.isCastType) {
                    if (isArrayType(hType)) {
                        hType = castArraySignaturToArrayExpr(hType);
                    }
                    // 等号左边两边可能出现类型不匹配的问题，例如等号右边是父类，等号左边为子类，这里进行强制类型转换
                    dslExpressionOfHId = "(" + hType + ")" + dslExpressionOfHId;
                }
                // 获得该对象的类型（在caller的局部变量表中）
//                String objTypeOfCallMethod = objId2objExpressionsMap.get(hId).objectType;

                // 如果函数调用语句传入的Type即objTypeOfCallMethod与 被调用函数的参数类型hType不匹配，要添加强制类型转换 (hType)
//                if (!objTypeOfCallMethod.equals(hType)) {
//                    dslExpressionOfHId = "(" + hType + ")" + dslExpressionOfHId;
//                }
                parametersDslList.add(dslExpressionOfHId);
            } else if (objId2objExpressionsMap.get(hId).fieldOfObjExpressionsPointToThisObjectList.size() != 0) {
                // 获得hId的DSL表示
                String dslExpressionOfHId = getDslExpressionOfObj(hId, objId2objExpressionsMap);
                if (DslProgramGen.isCastType) {
                    if (isArrayType(hType)) {
                        hType = castArraySignaturToArrayExpr(hType);
                    }
                    // 等号左边两边可能出现类型不匹配的问题，例如等号右边是父类，等号左边为子类，这里进行强制类型转换
                    dslExpressionOfHId = "(" + hType + ")" + dslExpressionOfHId;
                }
                parametersDslList.add(dslExpressionOfHId);
            } else if (objId2objExpressionsMap.get(hId).getTracesRecordList.size() != 0) {
                //f(A.b)
                //如果参数在getTracesList中，删除通过get获取的A.b记录
                String getTemp = objId2objExpressionsMap.get(hId).getTracesRecordList.get(0);
                // 获得该对象的类型
                String objTypeOfCallMethod = objId2objExpressionsMap.get(hId).objectType;
                if (DslProgramGen.isCastType) {
                    // 等号左边两边可能出现类型不匹配的问题，例如等号右边是父类，等号左边为子类，这里进行强制类型转换
                    if (isArrayType(hType)) {
                        hType = castArraySignaturToArrayExpr(hType);
                    }
                    getTemp = "(" + hType + ")" + getTemp;
                }
                // 如果函数调用语句传入的Type即objTypeOfCallMethod与 被调用函数的参数类型hType不匹配，要添加强制类型转换 (hType)
//                if (!objTypeOfCallMethod.equals(hType)) {
//                    getTemp = "(" + hType + ")" + getTemp;
//                }
                parametersDslList.add(getTemp);
                objId2objExpressionsMap.get(hId).getTracesRecordList.remove(0);
            } else {

            }
        }
        // 生成函数调用DSL语句
        String callMethodDsl = genCallMethodDslFromParameterListAndMethodName(currentMethodNode.newMethodName, parametersDslList);
        // 将调用的子函数名称和参数列表加入dslCalledSubMethodName2ParameterList
        dslCalledSubMethodName2ParameterList.put(currentMethodNode.newMethodName, parametersDslList);
        return callMethodDsl;
    }


    /**
     * 产生函数调用DSL语句（函数有返回值） 例如产生 Student stu = f(var1, var2); 函数调用DSL语句
     * 注意 如果参数有在 newForMethodParameterTempLocalRootList列表中，删除 tempStu (f(new Student();),并且生成tempStu=null的DSL语句
     *
     * @param currentMethodNode                    待处理的MethodNode B节点
     * @param dslMethodContentList                 由DSL语句表示的MethodNode A 内容列表
     * @param objId2objExpressionsMap              指定对象id在DSL程序中可以怎样表示出来 <objId, expressions>
     * @param dslObjFieldToObjIdMap                给定DSL语句（对象.field 或 对象数组[index]），看看该DSL语句指向哪个对象Id，主要是帮助处理Put与PutA的trace，减少查询时间
     * @param classTypeAndNumMap                   local root命名的辅助结构， 对于新建local root例如Student stu，如何命名stu，当前采用 "student"+num的方式表示
     * @param dslCalledSubMethodName2ParameterList 该函数调用的子函数名称及参数列表
     * @return 处理单个MethodNode可能跨越处理了其他TextNode的开始行，返回处理其他TextNode涉及的行数
     */
    public static int genCallMethodWithReturnDsl(MethodNode currentMethodNode, List<String> dslMethodContentList, Map<String, WhoPointToThisObjectCollection> objId2objExpressionsMap,
                                                 Map<String, String> dslObjFieldToObjIdMap,
                                                 Map<String, Long> classTypeAndNumMap, Map<String, List<String>> dslCalledSubMethodName2ParameterList) {
        List<String> newForMethodParameterTempLocalRootList = new ArrayList<>();
        List<String> methodReturnForMethodParameterTempLocalRootList = new ArrayList<>();
        // 生成等号右边的函数调用DSL语句
        String equalSignRightCallMethodDsl = genCallMethodEqualSignRightDsl(newForMethodParameterTempLocalRootList,
                methodReturnForMethodParameterTempLocalRootList,
                currentMethodNode,
                objId2objExpressionsMap, dslCalledSubMethodName2ParameterList);
        // 获得等号右边的函数的Return trace
        String equalSignRightReturnTrace = getReturnTraceFromMethodNode(currentMethodNode);
        // 获得等号右边函数Return的id
        String equalSignRightReturnObjId = Utils.RegularExpressionProcess(equalSignRightReturnTrace, "H:(.*?),");
        // 获得等号右边函数Return的类型
        String equalSignRightReturnObjType = Utils.RegularExpressionProcess(equalSignRightReturnTrace, "C:(.*?),");
        equalSignRightReturnObjType = renameClassNameReplaceSlashWithUnderlineAndRenameObjectClassName(equalSignRightReturnObjType);

        int returnNum = 0;

        // 生成等号左边的dsl语句
        String equalSignLeftDsl = "";
        // 获得左表达式类型
        StringBuilder equalSingLeftObjType = new StringBuilder();
        // 直接生成 Student stu = f(var); 这样的语句，定义一个局部变量存储函数的返回值
        // 给local root赋值例子 Add LR H:0x000000000d716361, C:java/lang/Class, T:main, R:0x000000004459eb14, M:java/util/ImmutableCollections$MapN.<init> ([Ljava/lang/Object;)V
        equalSignLeftDsl = genAddNewLocalRootDslStatementForMethodReturnEqualSignLeft(equalSignRightReturnObjId, equalSignRightReturnObjType,
                objId2objExpressionsMap,
                classTypeAndNumMap,
                equalSingLeftObjType);


        if (equalSignLeftDsl == null || "".equals(equalSignLeftDsl)) {
            // 添加等号右边DSL语句到函数体中
            dslMethodContentList.add(equalSignRightCallMethodDsl);
        } else {
            // 添加DSL语句到函数体中
            String dslStatement = null;
            if (DslProgramGen.isCastType && !equalSingLeftObjType.toString().equals("")) {
                dslStatement = equalSignLeftDsl + " = " + "(" + equalSingLeftObjType.toString() + ")" + equalSignRightCallMethodDsl;
            } else {
                dslStatement = equalSignLeftDsl + " = " + equalSignRightCallMethodDsl;
            }

            dslMethodContentList.add(dslStatement);
        }


        // 处理f(new Student())的情况新建的临时local root,生成tempStu=null的DSL语句
        for (String tempLocalRoot : newForMethodParameterTempLocalRootList) {
            dslMethodContentList.add(tempLocalRoot + " = null;");
        }
        // 处理f(g())的情况新建的临时local root,生成tempStu=null的DSL语句
        for (String tempLocalRoot : methodReturnForMethodParameterTempLocalRootList) {
            dslMethodContentList.add(tempLocalRoot + " = null;");
        }
        return returnNum;
    }
}
